﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Microsoft.Phone.Controls;

namespace Chenglin.WPJigsawPuzzle
{
    public partial class GamePage : PhoneApplicationPage
    {
        /// <summary>
        /// Defines the 4 directions that a block can be moved.
        /// </summary>
        private static readonly int[][] Directions = new int[4][] { new[] { 1, 0 }, new[] { -1, 0 }, new[] { 0, 1 }, new[] { 0, -1 } };

        private struct RowColumn
        {
            public int Row;
            public int Column;

            public override string ToString()
            {
                return string.Format("{0}-{1}", Row, Column);
            }
        }

        /// <summary>
        /// The private class is used to randomly sort the images.
        /// </summary>
        private class RankedElement
        {
            public int Rank;
            public FrameworkElement Element;
        }

        private bool _done;
        private Uri _imageUri;
        private int _level;
        private int _steps;
        private Timer _timer;
        private Stopwatch _stopwatch;

        public GamePage()
        {
            InitializeComponent();

            _timer = new Timer(TimerCallback, null, Timeout.Infinite, Timeout.Infinite);
            _stopwatch = new Stopwatch();

            this.Loaded += GamePage_Loaded;
            MainCanvas.LayoutUpdated += (sender, args) => { UpdateCanvasSize(); };
        }

        private void TimerCallback(object state)
        {
            TimeSpan ts = _stopwatch.Elapsed;
            Dispatcher.BeginInvoke(
                () =>
                {
                    StringBuilder sb = new StringBuilder(10);
                    sb.AppendFormat("{0:00}", ts.Hours);
                    sb.Append(':');
                    sb.AppendFormat("{0:00}", ts.Minutes);
                    sb.Append(':');
                    sb.AppendFormat("{0:00}", ts.Seconds);
                    sb.Append('.');
                    sb.Append(ts.Milliseconds / 100);
                    TimeTextBlock.Text = sb.ToString();
                });
        }

        private void GamePage_Loaded(object sender, RoutedEventArgs e)
        {
            BuildCanvas();
            UpdateCanvasSize();

            _stopwatch.Start();
            _timer.Change(50, 50);
        }

        /// <summary>
        /// Confirm whether the user wants to leave the page or not?
        /// </summary>
        protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
        {
            base.OnNavigatingFrom(e);

            if (!_done && e.IsCancelable)
            {
                var result = MessageBox.Show("拼图还未完成，您确认要放弃吗?", "确认", MessageBoxButton.OKCancel);
                if (result == MessageBoxResult.Cancel)
                {
                    e.Cancel = true;
                }
            }
        }

        /// <summary>
        /// Start to initialize the game canvas.
        /// </summary>
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            base.OnNavigatedTo(e);

            string str;
            if (!NavigationContext.QueryString.TryGetValue("level", out str) || !int.TryParse(str, out _level))
            {
                _level = 3; // the default level
            }

            int image = 1;
            if (!NavigationContext.QueryString.TryGetValue("img", out str) || !int.TryParse(str, out image))
            {
                image = 1; // the default image
            }
            _imageUri = new Uri(string.Format("Images/Image{0}.jpg", image), UriKind.Relative);
        }

        private void DrawLinesOnPreviewImage()
        {
            double width = PreviewImageCanvas.ActualWidth;
            Brush brush = new SolidColorBrush(SystemColors.GrayTextColor);
            for (int i = 1; i < _level; i++)
            {
                // Draw horizontal lines
                //
                double pos = width / _level * i;
                Line horizontalLine = new Line { Stroke = brush, StrokeThickness = 1, X1 = 0, X2 = width, Y1 = pos, Y2 = pos };
                Canvas.SetZIndex(horizontalLine, 1);
                PreviewImageCanvas.Children.Add(horizontalLine);

                // Draw vertical lines
                //
                Line verticalLine = new Line { Stroke = brush, StrokeThickness = 1, X1 = pos, X2 = pos, Y1 = 0, Y2 = width };
                Canvas.SetZIndex(verticalLine, 1);
                PreviewImageCanvas.Children.Add(verticalLine);
            }
        }

        private void SplitImage()
        {
            // Load the image into memory
            //
            var streamInfo = Application.GetResourceStream(_imageUri);
            BitmapImage bitmapImage = new BitmapImage();
            bitmapImage.SetSource(streamInfo.Stream);

            // Show the preview image
            //
            PreviewImage.Source = bitmapImage;

            Image imageBase = new Image();
            imageBase.Source = bitmapImage;
            imageBase.Stretch = Stretch.Fill;

            int imageWidth = bitmapImage.PixelWidth / _level;
            int imageHeight = bitmapImage.PixelHeight / _level;

            // Generate random ranks for each image.
            // They'll be used to sort these images.
            //
            Random rnd = new Random();

            int max = _level * _level - 1;
            List<RankedElement> list = new List<RankedElement>(max);
            for (int pos = 0; pos < max; pos++)
            {
                TranslateTransform transform = new TranslateTransform();
                transform.X = -imageWidth * (pos % _level);
                transform.Y = -imageHeight * (pos / _level);

                WriteableBitmap wb = new WriteableBitmap(imageWidth, imageHeight);
                wb.Render(imageBase, transform);
                wb.Invalidate();

                var image = new Image { Source = wb, Stretch = Stretch.Fill, Margin = new Thickness(1) };
                var imageBorder = new Border();
                imageBorder.BorderBrush = new SolidColorBrush(SystemColors.ActiveBorderColor);
                imageBorder.BorderThickness = new Thickness(1);
                imageBorder.Child = image;

                // The original position is stored in its Tag property.
                //
                imageBorder.Tag = pos;

                list.Add(new RankedElement { Element = imageBorder, Rank = rnd.Next() });
            }

            // help to release memory?
            imageBase.Source = null;
            imageBase = null;
            bitmapImage = null;

            list.Sort((x, y) => { return (x.Rank > y.Rank) ? 1 : ((x.Rank == y.Rank) ? 0 : -1); });

            // Show the randomly sorted images in the page
            //
            for (int pos = 0; pos < max; pos++)
            {
                int column = pos % _level;
                int row = pos / _level;

                FrameworkElement block = list[pos].Element;
                block.Tap += BlockImage_Tap;
                GameContainer.Children.Add(block);

                Grid.SetRow(block, row);
                Grid.SetColumn(block, column);
            }
        }

        /// <summary>
        /// Update the layout of the page. Usually this will be called after the page orientation is changed.
        /// </summary>
        private void UpdateCanvasSize()
        {
            double gridSize;
            double heightOffset;
            double widthOffset;
            if (MainCanvas.ActualHeight > MainCanvas.ActualWidth)
            {
                gridSize = MainCanvas.ActualWidth;
                heightOffset = (MainCanvas.ActualHeight - MainCanvas.ActualWidth) / 2;
                widthOffset = 0;
            }
            else
            {
                gridSize = MainCanvas.ActualHeight;
                heightOffset = 0;
                widthOffset = (MainCanvas.ActualWidth - MainCanvas.ActualHeight) / 2;
            }

            GameContainer.Width = gridSize;
            GameContainer.Height = gridSize;
            Canvas.SetLeft(GameContainer, widthOffset);
            Canvas.SetTop(GameContainer, heightOffset);
        }

        private void BuildCanvas()
        {
            // Make sure all elements in the canvas is cleared.
            //
            GameContainer.Children.Clear();
            GameContainer.RowDefinitions.Clear();
            GameContainer.ColumnDefinitions.Clear();

            GridLength star = new GridLength(1, GridUnitType.Star);
            for (int i = 0; i < _level; i++)
            {
                GameContainer.RowDefinitions.Add(new RowDefinition { Height = star });
                GameContainer.ColumnDefinitions.Add(new ColumnDefinition { Width = star });
            }

            SplitImage();

            DrawLinesOnPreviewImage();
        }

        private void BlockImage_Tap(object sender, System.Windows.Input.GestureEventArgs e)
        {
            if (_done)
            {
                return;
            }

            FrameworkElement srcElement = sender as FrameworkElement;
            int originalPos = (int)srcElement.Tag;

            // Loop through all images to remember their current positions
            //
            List<RowColumn> list = new List<RowColumn>(GameContainer.Children.Count - 1);
            foreach (FrameworkElement elm in GameContainer.Children)
            {
                if (originalPos != (int)elm.Tag)
                {
                    RowColumn rc;
                    rc.Row = Grid.GetRow(elm);
                    rc.Column = Grid.GetColumn(elm);
                    list.Add(rc);
                }
            }

            int currentRow = Grid.GetRow(srcElement);
            int currentColumn = Grid.GetColumn(srcElement);
            bool moved = false;
            foreach (var direction in Directions)
            {
                int row = currentRow + direction[0];
                int column = currentColumn + direction[1];

                if (row >= 0 && row < _level && column >= 0 && column < _level)
                {
                    bool occupied = false;
                    foreach (RowColumn rc in list)
                    {
                        if (rc.Row == row && rc.Column == column)
                        {
                            occupied = true;
                            break;
                        }
                    }

                    if (!occupied)
                    {
                        // OK, we can do the move now!
                        if (direction[0] != 0)
                        {
                            Grid.SetRow(srcElement, row);
                        }
                        if (direction[1] != 0)
                        {
                            Grid.SetColumn(srcElement, column);
                        }

                        moved = true;
                        break;
                    }
                }
            }

            if (moved)
            {
                _steps++;
                this.StepTextBlock.Text = string.Format("{0} 步", _steps);

                bool success = true;
                foreach (FrameworkElement elm in GameContainer.Children)
                {
                    int row = Grid.GetRow(elm);
                    int column = Grid.GetColumn(elm);
                    if (row * _level + column != (int)elm.Tag)
                    {
                        success = false;
                        break;
                    }
                }

                if (success)
                {
                    // The user already won the game!
                    //
                    // stop the timer
                    _done = true;
                    _stopwatch.Stop();
                    _timer.Change(Timeout.Infinite, Timeout.Infinite);

                    TimeSpan timeSpan = _stopwatch.Elapsed;
                    StringBuilder sb = new StringBuilder();
                    sb.Append("您已经成功完成拼图！一共用时 ");
                    int hours = (int)timeSpan.TotalHours;
                    if (hours > 0)
                    {
                        sb.Append(hours);
                        sb.Append(" 小时 ");
                    }
                    if (timeSpan.Minutes > 0)
                    {
                        sb.Append(timeSpan.Minutes);
                        sb.Append(" 分 ");
                    }
                    else if (hours > 0)
                    {
                        sb.Append(" 0 分 ");
                    }
                    sb.Append(timeSpan.Seconds);
                    sb.Append(" 秒。");

                    MessageBox.Show(sb.ToString(), "恭喜！", MessageBoxButton.OK);
                }
            }
        }
    }
}